/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


// cl_game.cpp -- interface to game library


#include "client.h"


static void *		cg_libHandle;
static cgImport_t	cg_framework;

cgExport_t *		cg = NULL;


/*
 ==================
 CG_SetDeltaAngles
 ==================
*/
static void CG_SetDeltaAngles (const int deltaAngles[3]){

	cl.gameDeltaAngles[0] = M_ClampShort(deltaAngles[0]);
	cl.gameDeltaAngles[1] = M_ClampShort(deltaAngles[1]);
	cl.gameDeltaAngles[2] = M_ClampShort(deltaAngles[2]);
}

/*
 ==================
 CG_SetSensitivity
 ==================
*/
static void CG_SetSensitivity (float sensitivity){

	cl.gameSensitivity = sensitivity;
}

/*
 ==================
 CG_UpdateLoading
 ==================
*/
static void CG_UpdateLoading (void){

	if (cls.state != CS_LOADING)
		return;

	// Pump events
	Sys_GenerateEvents();

	// Update the screen
	CL_UpdateScreen();
}

/*
 ==================
 CG_LoadLevel
 ==================
*/
void CG_LoadLevel (void){

	// Make sure sounds aren't playing
	S_StopAllSounds();

	// Load the level
	cls.state = CS_LOADING;

	GUI_Activate(GUI_LOADING);

	cg->LoadLevel();

	// Start sending user commands
	cls.state = CS_PRIMED;

	// Clear key and mouse input states
	Key_ClearStates();
	IN_ClearStates();

	// Force GUI and console off
	GUI_Activate(GUI_NONE);
	Con_Close();

	// Make sure everything is paged in
	Mem_TouchMemory();
}

/*
 ==================
 CG_FreeLevel
 ==================
*/
void CG_FreeLevel (void){

	cg->FreeLevel();
}

/*
 ==================
 CG_SetTime

 Sets and adjusts the client's view of server time.

 We attempt to have cl.time exactly equal the server's view of time plus
 cl_timeNudge, but with variable latencies over the internet it will often need
 to drift a bit to match conditions.

 Our ideal time would be to have the adjusted time approach, but not pass, the
 very latest snapshot.

 Adjustments are only made when a new snapshot arrives with a rational latency,
 which keeps the adjustment process framerate independent and prevents massive
 overadjustment during times of significant packet loss or bursted delayed
 packets.
 ==================
*/
static void CG_SetTime (void){

	int		timeDelta, timeDeltaDelta;

	if (cl_timeNudge->integerValue < -30)
		CVar_SetInteger(cl_timeNudge, -30);
	else if (cl_timeNudge->integerValue > 30)
		CVar_SetInteger(cl_timeNudge, 30);

	cl.time = cls.frameTime + cl.timeDelta - cl_timeNudge->integerValue;

	// Guarantee that time will never flow backwards, even if timeDelta made an
	// adjustment or cl_timeNudge was changed
	if (cl.time < cl.oldTime)
		cl.time = cl.oldTime;

	cl.oldTime = cl.time;

	// Note if we are almost past the latest frame (without cl_timeNudge), so
	// we will try and adjust back a bit when the next snapshot arrives
	if (cls.frameTime + cl.timeDelta >= cl.snapshotTime - 5)
		cl.snapshotExtrapolated = true;

	// If we have gotten new snapshots, drift timeDelta
	if (cl.snapshotAvailable){
		cl.snapshotAvailable = false;

		// If the current time is way off, just correct to the current value
		timeDelta = cl.snapshotTime - cls.frameTime;
		timeDeltaDelta = M_Abs(timeDelta - cl.timeDelta);

		if (timeDeltaDelta > 500){
			cl.time = cl.snapshotTime;
			cl.oldTime = cl.snapshotTime;

			cl.timeDelta = timeDelta;

			if (cl_showTimeDelta->integerValue)
				Com_Printf("<RESET> ");
		}
		else if (timeDeltaDelta > 100){
			cl.timeDelta = (cl.timeDelta + timeDelta) >> 1;

			if (cl_showTimeDelta->integerValue)
				Com_Printf("<FAST> ");
		}
		else {
			// If any of the frames between this and the previous snapshot had
			// to be extrapolated, nudge our sense of time back a little.
			// The granularity of +1 / -2 is too high for com_timeScale
			// modified frame times.
			if (com_timeScale->floatValue == 1.0f){
				if (cl.snapshotExtrapolated){
					cl.snapshotExtrapolated = false;

					cl.timeDelta -= 2;
				}
				else
					cl.timeDelta += 1;
			}
		}

		if (cl_showTimeDelta->integerValue)
			Com_Printf("%i\n", cl.timeDelta);
	}
}

/*
 ==================
 CG_RunFrame
 ==================
*/
void CG_RunFrame (void){

	if (cls.state == CS_PRIMED){
		
		// Getting a valid snapshot ends the connection process
		if (cl.snapshotAvailable){
			cl.snapshotAvailable = false;

			cls.state = CS_ACTIVE;

			cl.time = cl.snapshotTime;
			cl.oldTime = cl.snapshotTime;

			cl.timeDelta = cl.snapshotTime - cls.frameTime;

		}
	}

	if (cls.state != CS_ACTIVE)
		return;

	// Get our current view of time
	CG_SetTime();

	// Run the game simulation
	cg->RunFrame(cl.time, &cl.userCmds[cl.userCmdNumber & USERCMD_MASK]);
}


// ============================================================================


/*
 ==================
 CG_SetupFramework
 ==================
*/
#undef LoadLibrary
#undef CopyFile
static void CG_SetupFramework (void){

	// Common functions
	cg_framework.odSystem.Printf = Com_Printf;
	cg_framework.odSystem.DPrintf = Com_DPrintf;
	cg_framework.odSystem.Error = Com_Error;

	// Memory functions
	cg_framework.odMemory.Alloc = Mem_Alloc;
	cg_framework.odMemory.ClearedAlloc = Mem_ClearedAlloc;
	cg_framework.odMemory.Free = Mem_Free;
	cg_framework.odMemory.TagFree = Mem_TagFree;
	cg_framework.odMemory.Size = Mem_Size;
	cg_framework.odMemory.TagSize = Mem_TagSize;
	cg_framework.odMemory.CopyString = Mem_CopyString;

	// File system functions
	cg_framework.odFileSystem.Read = FS_Read;
	cg_framework.odFileSystem.Write = FS_Write;
	cg_framework.odFileSystem.Printf = FS_Printf;
	cg_framework.odFileSystem.Seek = FS_Seek;
	cg_framework.odFileSystem.Tell = FS_Tell;
	cg_framework.odFileSystem.Flush = FS_Flush;
	cg_framework.odFileSystem.ForceFlush = FS_ForceFlush;
	cg_framework.odFileSystem.OpenFile = FS_OpenFile;
	cg_framework.odFileSystem.CloseFile = FS_CloseFile;
	cg_framework.odFileSystem.ReadFile = FS_ReadFile;
	cg_framework.odFileSystem.FreeFile = FS_FreeFile;
	cg_framework.odFileSystem.WriteFile = FS_WriteFile;
	cg_framework.odFileSystem.CopyFile = FS_CopyFile;
	cg_framework.odFileSystem.RenameFile = FS_RenameFile;
	cg_framework.odFileSystem.RemoveFile = FS_RemoveFile;
	cg_framework.odFileSystem.FileExists = FS_FileExists;
	cg_framework.odFileSystem.ListFiles = FS_ListFiles;
	cg_framework.odFileSystem.ListFilteredFiles = FS_ListFilteredFiles;
	cg_framework.odFileSystem.FreeFileList = FS_FreeFileList;

	// Command system functions
	cg_framework.odCmdSystem.AddCommand = Cmd_AddCommand;
	cg_framework.odCmdSystem.RemoveCommand = Cmd_RemoveCommand;
	cg_framework.odCmdSystem.Argc = Cmd_Argc;
	cg_framework.odCmdSystem.Argv = Cmd_Argv;
	cg_framework.odCmdSystem.Args = Cmd_Args;
	cg_framework.odCmdSystem.TokenizeString = Cmd_TokenizeString;
	cg_framework.odCmdSystem.ExecuteText = Cmd_ExecuteText;

	// CVar system functions
	cg_framework.odCVarSystem.Get = CVar_Get;
	cg_framework.odCVarSystem.SetString = CVar_SetString;
	cg_framework.odCVarSystem.SetFloat = CVar_SetFloat;
	cg_framework.odCVarSystem.SetInteger = CVar_SetInteger;
	cg_framework.odCVarSystem.FindVariable = CVar_FindVariable;
	cg_framework.odCVarSystem.GetVariableString = CVar_GetVariableString;
	cg_framework.odCVarSystem.GetVariableFloat = CVar_GetVariableFloat;
	cg_framework.odCVarSystem.GetVariableInteger = CVar_GetVariableInteger;
	cg_framework.odCVarSystem.SetVariableString = CVar_SetVariableString;
	cg_framework.odCVarSystem.SetVariableFloat = CVar_SetVariableFloat;
	cg_framework.odCVarSystem.SetVariableInteger = CVar_SetVariableInteger;
	cg_framework.odCVarSystem.AllowCheats = CVar_AllowCheats;
	cg_framework.odCVarSystem.GetModifiedFlags = CVar_GetModifiedFlags;
	cg_framework.odCVarSystem.SetModifiedFlags = CVar_SetModifiedFlags;
	cg_framework.odCVarSystem.ClearModifiedFlags = CVar_ClearModifiedFlags;

	// Parser functions
	cg_framework.odParser.ReadToken = PS_ReadToken;
	cg_framework.odParser.UnreadToken = PS_UnreadToken;
	cg_framework.odParser.ReadDouble = PS_ReadDouble;
	cg_framework.odParser.ReadFloat = PS_ReadFloat;
	cg_framework.odParser.ReadUnsignedInt = PS_ReadUnsignedInt;
	cg_framework.odParser.ReadSignedInt = PS_ReadSignedInt;
	cg_framework.odParser.ExpectTokenString = PS_ExpectTokenString;
	cg_framework.odParser.ExpectTokenType = PS_ExpectTokenType;
	cg_framework.odParser.CheckTokenString = PS_CheckTokenString;
	cg_framework.odParser.CheckTokenType = PS_CheckTokenType;
	cg_framework.odParser.PeekTokenString = PS_PeekTokenString;
	cg_framework.odParser.PeekTokenType = PS_PeekTokenType;
	cg_framework.odParser.SkipUntilString = PS_SkipUntilString;
	cg_framework.odParser.SkipRestOfLine = PS_SkipRestOfLine;
	cg_framework.odParser.SkipBracedSection = PS_SkipBracedSection;
	cg_framework.odParser.ScriptWarning = PS_ScriptWarning;
	cg_framework.odParser.ScriptError = PS_ScriptError;
	cg_framework.odParser.ResetScript = PS_ResetScript;
	cg_framework.odParser.EndOfScript = PS_EndOfScript;
	cg_framework.odParser.SetScriptFlags = PS_SetScriptFlags;
	cg_framework.odParser.SetPunctuationsTable = PS_SetPunctuationsTable;
	cg_framework.odParser.LoadScriptFile = PS_LoadScriptFile;
	cg_framework.odParser.LoadScriptMemory = PS_LoadScriptMemory;
	cg_framework.odParser.FreeScript = PS_FreeScript;

	// System functions
	cg_framework.odSystem.LoadLibrary = Sys_LoadLibrary;
	cg_framework.odSystem.FreeLibrary = Sys_FreeLibrary;
	cg_framework.odSystem.GetProcAddress = Sys_GetProcAddress;
	cg_framework.odSystem.Milliseconds = Sys_Milliseconds;
	cg_framework.odSystem.GetClockTicks = Sys_GetClockTicks;
	cg_framework.odSystem.ClockTicksPerSecond = Sys_ClockTicksPerSecond;
	cg_framework.odSystem.GetProcessorId = Sys_GetProcessorId;
	cg_framework.odSystem.GetProcessorString = Sys_GetProcessorString;

	// Collision functions
	cg_framework.odCollision.LoadMap = CM_LoadMap;
	cg_framework.odCollision.RegisterModel = CM_RegisterModel;
	cg_framework.odCollision.SetupBoxModel = CM_SetupBoxModel;
	//cg_framework.odCollision.InstantiateDynamicModel = CM_InstantiateDynamicModel;
	cg_framework.odCollision.GetModelContents = CM_GetModelContents;
	cg_framework.odCollision.GetModelBounds = CM_GetModelBounds;
	cg_framework.odCollision.Contents = CM_Contents;
	cg_framework.odCollision.Trace = CM_Trace;
	cg_framework.odCollision.PointInLeaf = CM_PointInLeaf;
	cg_framework.odCollision.LeafCluster = CM_LeafCluster;
	cg_framework.odCollision.LeafArea = CM_LeafArea;
	cg_framework.odCollision.LeafCanSeePortalSky = CM_LeafCanSeePortalSky;
	cg_framework.odCollision.ClusterPVS = CM_ClusterPVS;
	cg_framework.odCollision.PointInPVS = CM_PointInPVS;
	cg_framework.odCollision.BoundsInPVS = CM_BoundsInPVS;
	cg_framework.odCollision.NumPortals = CM_NumPortals;
	cg_framework.odCollision.FindPortal = CM_FindPortal;
	cg_framework.odCollision.SetPortalState = CM_SetPortalState;
	cg_framework.odCollision.GetPortalState = CM_GetPortalState;
	cg_framework.odCollision.WritePortalBits = CM_WritePortalBits;
	cg_framework.odCollision.AreasAreConnected = CM_AreasAreConnected;
	cg_framework.odCollision.NumAreas = CM_NumAreas;
	cg_framework.odCollision.PointInArea = CM_PointInArea;
	cg_framework.odCollision.BoundsInAreas = CM_BoundsInAreas;
	cg_framework.odCollision.NumPortalsInArea = CM_NumPortalsInArea;
	cg_framework.odCollision.GetPortalFromArea = CM_GetPortalFromArea;
	cg_framework.odCollision.WriteAreaBits = CM_WriteAreaBits;

	// Renderer functions
	cg_framework.odRenderer.LoadMap = R_LoadMap;
	cg_framework.odRenderer.RegisterModel = R_RegisterModel;
	cg_framework.odRenderer.RegisterMaterial = R_RegisterMaterial;
	cg_framework.odRenderer.RegisterMaterialLight = R_RegisterMaterialLight;
	cg_framework.odRenderer.RegisterMaterialNoMip = R_RegisterMaterialNoMip;
	cg_framework.odRenderer.AllocRenderScene = R_AllocRenderScene;
	cg_framework.odRenderer.FreeRenderScene = R_FreeRenderScene;
	cg_framework.odRenderer.AddRenderEntity = R_AddRenderEntity;
	cg_framework.odRenderer.UpdateRenderEntity = R_UpdateRenderEntity;
	cg_framework.odRenderer.FreeRenderEntity = R_FreeRenderEntity;
	cg_framework.odRenderer.AddRenderLight = R_AddRenderLight;
	cg_framework.odRenderer.UpdateRenderLight = R_UpdateRenderLight;
	cg_framework.odRenderer.FreeRenderLight = R_FreeRenderLight;
	cg_framework.odRenderer.AddRenderParticle = R_AddRenderParticle;
	cg_framework.odRenderer.UpdateRenderParticle = R_UpdateRenderParticle;
	cg_framework.odRenderer.FreeRenderParticle = R_FreeRenderParticle;
	cg_framework.odRenderer.ProjectDecalOntoWorld = R_ProjectDecalOntoWorld;
	cg_framework.odRenderer.ProjectDecal = R_ProjectDecal;
	cg_framework.odRenderer.RemoveDecals = R_RemoveDecals;
	cg_framework.odRenderer.CompileLights = R_CompileLights;
	cg_framework.odRenderer.RenderScene = R_RenderScene;
	cg_framework.odRenderer.SetColor = R_SetColor;
	cg_framework.odRenderer.SetColor4 = R_SetColor4;
	cg_framework.odRenderer.DrawStretchPic = R_DrawStretchPic;
	cg_framework.odRenderer.DrawShearedPic = R_DrawShearedPic;
	cg_framework.odRenderer.CropRenderSize = R_CropRenderSize;
	cg_framework.odRenderer.UnCrop = R_UnCrop;
	cg_framework.odRenderer.CaptureRenderToTexture = R_CaptureRenderToTexture;
	cg_framework.odRenderer.UpdateTextureImage = R_UpdateTextureImage;
	cg_framework.odRenderer.GetGLConfig = R_GetGLConfig;

	// Sound functions
	cg_framework.odSound.LoadReverbs = S_LoadReverbs;
	cg_framework.odSound.RegisterSoundShader = S_RegisterSoundShader;
	cg_framework.odSound.AddSoundEmitter = S_AddSoundEmitter;
	cg_framework.odSound.UpdateSoundEmitter = S_UpdateSoundEmitter;
	cg_framework.odSound.FreeSoundEmitter = S_FreeSoundEmitter;
	cg_framework.odSound.PlaySoundOnEmitter = S_PlaySoundOnEmitter;
	cg_framework.odSound.StopSoundOnEmitter = S_StopSoundOnEmitter;
	cg_framework.odSound.PlaceListener = S_PlaceListener;
	cg_framework.odSound.ShakeAmplitudeForListener = S_ShakeAmplitudeForListener;
	cg_framework.odSound.PlayLocalSound = S_PlayLocalSound;
	cg_framework.odSound.PlayBackgroundMusic = S_PlayBackgroundMusic;
	cg_framework.odSound.StopBackgroundMusic = S_StopBackgroundMusic;
	cg_framework.odSound.GetALConfig = S_GetALConfig;

	// Editor functions
	cg_framework.odEditor.LaunchEditor = ED_LaunchEditor;
	cg_framework.odEditor.CloseEditor = ED_CloseEditor;
	cg_framework.odEditor.DrawEditBox = ED_DrawEditBox;
	cg_framework.odEditor.SetLightCallbacks = ED_SetLightCallbacks;
	cg_framework.odEditor.EditLightProperties = ED_EditLightProperties;
	cg_framework.odEditor.SetReverbCallbacks = ED_SetReverbCallbacks;
	cg_framework.odEditor.EditReverbProperties = ED_EditReverbProperties;

	// Client functions
	cg_framework.SetDeltaAngles = CG_SetDeltaAngles;
	cg_framework.SetSensitivity = CG_SetSensitivity;
	cg_framework.UpdateLoading = CG_UpdateLoading;
}

/*
 ==================
 CG_Init
 ==================
*/
void CG_Init (void){

	GETCGAMEAPI	GetCGameAPI;

	// Free anything we have
	if (cg_libHandle)
		CG_Shutdown();

	// Set up the framework for the library
	CG_SetupFramework();

	// Load the game library
	Com_Printf("Loading client game library...\n");

	cg_libHandle = Sys_LoadLibrary("cgame");
	if (!cg_libHandle)
		Com_Error(true, "Could not load client game library");

	// Get the game API
	GetCGameAPI = (GETCGAMEAPI)Sys_GetProcAddress(cg_libHandle, "GetCGameAPI");
	if (!GetCGameAPI){
		Sys_FreeLibrary(cg_libHandle);
		cg_libHandle = NULL;

		Com_Error(true, "Could not get client game API");
	}

	cg = GetCGameAPI(&cg_framework);

	if (cg->apiVersion != CGAME_API_VERSION)
		Com_Error(true, "Client game interface version is %i, expected version %i", cg->apiVersion, CGAME_API_VERSION);

	// Initialize the game
	cg->Init();
}

/*
 ==================
 CG_Shutdown
 ==================
*/
void CG_Shutdown (void){

	if (!cg_libHandle)
		return;

	// Shutdown the game
	cg->Shutdown();

	// Free the game library
	Com_Printf("Unloading client game library...\n");

	Sys_FreeLibrary(cg_libHandle);
	cg_libHandle = NULL;

	cg = NULL;
}
